Repository navigation
fix(native): recover unattended sync helper setup - #681
Farhan (farhankhalaf) wants to merge 2 commits into
Conversation
🎨 Storybook previewUpdated for 4e5f802
|
📋 PR Overview
🔬 Coverage
|
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9a9974b350
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "Codex (@codex) review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".
|
Follow-up on the activation-wait and timing questions:
Source: helper wait behavior, client reply timeout. |
|
Follow-up is blocked only on #682's current Danger run, which is queued with no ARC runner assigned. The previous metadata run waited 20 minutes before the status edit superseded it. CI maintainers need to inspect ARC capacity/dispatch, get the current run executed, and check its coverage and Danger results. Validation remains incomplete until then. Heads are unchanged: #681
Both CI checklists are updated, with #682 explicitly incomplete and native evidence limits preserved. No source edits, pushes or merges. Human review remains requested from czxtm, scottmcmaster and arximboldi. |
|
| if nothing_more_to_do(&report) { | ||
| presentation.note_settled_report(&report, attempt_generation); | ||
| publish(&report); | ||
| return; |
There was a problem hiding this comment.
Old status hides newer failure
The generation check protects the saved failure, but not the row that drive_with_clock publishes. If the loop gets AtThisBuild, then a newer Disable attempt fails before publication, the loop still publishes the old Ready row and exits. The newer action cannot start another loop while CONVERGING is set. This hides the failed removal and can stop automatic recovery.
Skip stale reports before publishing or exiting, and ensure the newer attempt gets another pass. Update the race test to check the actual published row rather than a separate synthetic failure.
There was a problem hiding this comment.
Fixed in 7ae7c85. A report whose generation changed during the pass is skipped, and the loop observes again instead of exiting. After a settled publish it exits only if the generation is unchanged, and if an action starts while a row is being published the loop re-observes right away. a_stale_ready_row_never_hides_a_newer_failed_action asserts on the published rows (Stopped, then Removed).
| setNotice({ | ||
| tone: "error", | ||
| message: `Retrying the unattended sync helper failed: ${error instanceof Error ? error.message : String(error)}`, | ||
| }); |
There was a problem hiding this comment.
Retry bypasses shared diagnostics
The new Retry error is defined directly in PermissionsPanel. The repository requires user-facing TypeScript errors to be defined and reported through nostics in errors.ts, with a diagnostic code, title, and suggestion. Add the Retry diagnostic there and use it here instead of the ad hoc message. This repository requirement must be satisfied before merging.
Context Used: TypeScript error handling with nostics in the native app (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed in 4e5f802. Retry failures from this panel and from the repair banner now report through nostics as HELPER_E001 in errors.ts, with its explanation and fix.
cooper (czxtm)
left a comment
There was a problem hiding this comment.
Requesting changes: this needs a rebase onto main and a decision on registration policy, because #691 (f28a2e2, "Register valid helpers on explicit Enable") already landed an incompatible fix for the same notFound problem and the branch conflicts with it in 5 files (protocol.rs, reconcile.rs, service.rs, permissions-panel.tsx and its test).
Blocking
- Registration policy.
convergeregisters on(NotRegistered | NotFound, Goal::Install)with no distinction between a background status check and a user click (reconcile.rs#L531-L534). With a saved Enable preference, any observation or permissions refresh can call register without a click. Main splits this withenum Invocation, andInvocation::ObservereturnsReconciled::RegistrationUnavailableand never registers. Please rebase, adopt main's explicit-Enable policy unless you want to make the case for background registration, and route the newhelperRetrythrough the Enable invocation when the saved decision is Enable. The tests that expect background re-registration should go. - Bundle validation.
check_service_definition(service.rs#L68-L115) reads withfs::readandfs::metadata, which follow symlinks, and it rejectsProgrambut notProgramArguments. The helper runs as root, so a bundle whose plist or binary resolves outside the bundle, or which carriesProgramArguments, passes this check. Drop it and keep main'svalidate_bundled_definition, which canonicalizes, requires regular files inside the bundle, and rejects both keys. If you want an Unavailable versus Invalid split, layer it on top of that.
Two Greptile threads are still open (the stale-Ready publish race in drive_with_clock, and the Retry diagnostics one). The race is a merge blocker for me too, and the fix needs a regression test that asserts on the published row.
Smaller things
- After the rebase,
phase_with_failure_visibilityand thedebug_assert!indrive_with_clockneed to handleReconciled::RegistrationUnavailableexplicitly, with a phase that offers Enable or Retry. Otherwise it can fail to compile or fall into a wildcard arm. - Danger flags no Linear ID, and the PR bundles the registration fix, typed lifecycle, Retry UI, diagnostics and a Clippy cleanup. Once the validation piece is dropped, splitting the backend phase and retry from the UI and diagnostics would help, since #682 is stacked on this.
- #682 will need restacking after this lands, and the test results were produced before the rebase onto #691, so please refresh them on the new head.
…current Port the helper recovery backend onto the explicit-Enable registration policy. Observation still never registers a missing helper. - Add a Retry action that carries out the stored decision. A stored Enable retries as an explicit Enable, so a missing registration gets one new register. A stored Disable retries the removal. An undecided user is handled exactly like an observation: Retry never registers a missing helper, though it adopts an existing registration the same way an observation does. Retry never opens System Settings. - After a register error, judge the result from one fresh status read (approval, enabled and verified, or still missing) and keep the register error in the report instead of issuing a second register. - Report a typed HelperPermissionPhase on the permission row and on helper action results. RegistrationUnavailable maps to Failed, which offers Retry. While a stored Disable is in progress, waiting on an activation or failed, the row describes the removal instead of setup. - Classify every report into an exhaustive loop step instead of relying on a debug assertion. - Skip a convergence report that predates a newer user action, so a stale Ready row can no longer hide a failed Disable. A start request that arrives while the loop is ending is recorded and served, and each pass acknowledges the requests it serves, so no request is dropped and none buys a second attempt budget. - Busy observations preserve the last meaningful row, and the last helper failure is included in the opt-in feedback snapshot. - Regenerate the oRPC and specta TypeScript bindings, and give the existing panel test's HelperReport the new required phase so this commit typechecks on its own. - Remove a redundant `.nixmacignore` binding that strict Clippy rejects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…backend - The permissions row and the repair banner read the typed helper phase: approval, setup progress, an active build or restore, a needed user action, removal and failure each get their own copy and action. - A failed phase, including a missing registration, offers Retry, which calls helperRetry and keeps the saved Enable or Disable decision. Disable stays available for a wanted helper. No Enable is offered while a stored Disable has not finished; a failed removal offers Retry. - Helper action notices follow the live row instead of copying an earlier request result. While an Enable replaces the previous row, the banner shows setup progress rather than a failure. - A failed Retry from the panel or the repair banner is reported through the HELPER_E001 nostics diagnostic. The banner clears that error when its failure episode ends, and ignores a Retry that settles after its episode ended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9383d71 to
4e5f802
Compare
|
Rebuilt on main (1492663) as two commits: the backend (7ae7c85) and the UI (4e5f802).
Local test results on the new head are in the description. The signed-app helper smoke on a real Mac has not been rerun on this head. #682 will be restacked once this lands. |
|








Summary
Builds on main's explicit-Enable registration policy. #682 is stacked on this branch and will be restacked after this lands. Linear: ENG-1447.
Helper registration can look like a persistent failure while macOS is still settling, or lose a newer failure during concurrent checks. Approval, setup progress, active-sync waits, manual remediation and failure now have distinct phases and matching actions, and removal has its own copy. Retry repeats the stored decision: a stored Enable retries as an explicit Enable, a stored Disable retries the removal, and an unset decision is handled like an observation. Observation never registers a missing helper. Busy observations preserve the last meaningful status, and a report from before a newer action is skipped rather than published.
Registration errors keep their diagnostic detail, including when macOS reports Enabled but helper verification then fails. Helper action messages follow the live permission row, and Retry failures from the panel and the repair banner report through the
HELPER_E001diagnostic. Bundle validation is main'svalidate_bundled_definition, unchanged. A small cleanup removes a redundant.nixmacignorebinding so strict Clippy passes.The change is two commits: the backend (7ae7c85) and the UI (4e5f802). They ship together because the backend alone would show "Try again" copy with no Retry button.
Test Plan
cargo test -p nixmac1,130 passed, 10 ignored; nixmac-helper 77 and nixmac-sync-agent 49 passed. Strict workspace Clippy with all targets and codegen and the rustfmt check passed.system-defaults-ctatests that fail identically on main under local Node 25 (localStorage.clear is not a function).Docs
🤖 Generated with Claude Code
https://claude.ai/code/session_01GziRseGZMsJ5Yf7EboZLMc